using System;
using System.IO;
using System.Collections;
using System.Diagnostics;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.Geometry.Utils;

namespace Petra.Gis.DataServices.SHP
{
  /// <summary>
  /// Converts a Shapefile point to a OGIS Polygon.
  /// </summary>
  partial class GeometryFactory : IGeometryFactory
  {


   /// <summary>
    /// Reads a stream and converts the shapefile record to an equilivent geometry object.
    /// </summary>
    /// <param name="file">The stream to read.</param>
    /// <param name="geometryFactory">The geometry factory to use when making the object.</param>
    /// <returns>The Geometry object that represents the shape file record.</returns>
    public static GeometryBase binaryToPolygon(byte[] data)
    {
      BinaryReader br = new BinaryReader(new MemoryStream(data));

      ShapeGeometryTypes shapeType = (ShapeGeometryTypes)br.ReadInt32();
      if (!(shapeType == ShapeGeometryTypes.Polygon || shapeType != ShapeGeometryTypes.PolygonM ||
               shapeType != ShapeGeometryTypes.PolygonZ))
        throw new ApplicationException("Attempting to load a non-polygon as polygon.");

      // Read and for now ignore bounds.
      //double[] box = new double[4];
      //for (int i = 0; i < 4; i++)
      //  box[i] = br.ReadDouble();
      for (int i = 0; i < 4; i++)
        br.ReadDouble();

      int[] partOffsets;
      //  Read total number of component in this geometry
      int numParts = br.ReadInt32();
      // read number of all points in this geom
      int numPoints = br.ReadInt32();

      // read polygon component offsets
      // polygon is composed from rings
      // each ring has specified num of points
      partOffsets = new int[numParts];
      for (int i = 0; i < numParts; i++)
        partOffsets[i] = br.ReadInt32();

      // now parse polygons
      Polygon pout = null;
      MultiArea maOut = new MultiArea();
      int numRingPoints = 0, start, finish;

      // for all rings in blob...
      //for (int part = 0; part < numParts - 1; part++)
      for (int part = 0; part < numParts; part++)
      {
        start = partOffsets[part];
        if (part == numParts - 1)
          finish = numPoints;
        else
          finish = partOffsets[part + 1];

        numRingPoints = finish - start;

        // get num of points for this ring
        // and copy points into coordinate list
        //if (numParts == 1)
        //  numRingPoints = numPoints;
        //else
        //  numRingPoints = (partOffsets[part + 1] - partOffsets[part]);
        //byte[] pts = br.ReadBytes(numRingPoints * 16);

        CoordinateList clist = new CoordinateList(data, (int)br.BaseStream.Position, numRingPoints, Dimension.dim2D);

        // if geometry has multiple parts
        // must seek to next part begin point
        if (part < numParts - 1)
          br.BaseStream.Seek(numRingPoints * 16, SeekOrigin.Current);

        //for (int i = 0; i < numRingPoints; i++)
        //{
        //  clist[i].X = br.ReadDouble();
        //  clist[i].X = br.ReadDouble();
        //}

        LineRing nr = new LineRing();
        nr.coordList = clist;
        if (!SurfaceMath.IsCCW(nr))
        {
          // not ccw, is new polygon
          // if no polygon exists, create new
          if (pout == null)
          {
            pout = new Polygon();
            // add new polygon into MultiArea
            maOut.Add(pout);
            // add this ring into polygon
            pout.Add(nr);
          }
          else
          {
            // ok polygon exists
            // finish it, add last ring
            pout.Add(nr);
            // put this polygon into Multiarea
            //maOut.Add(pout);
            // create new polygon
            pout = null;
          }
        }
        else
        { 
          // is ccw new hole in polygon
          if (pout == null)
            throw new InvalidDataException("hole before polygon");
          else
          {
            //finish prev polyg.
            pout.Add(nr);
          }          
        }

      }

      if (maOut.Count > 1)
        return maOut;
      else
        return maOut[0];

    }









    public static byte[] polygonToBinary(GeometryBase geometry)
    {

      return new byte[0];

      //GeometryCollection multi;
      //if (geometry is GeometryCollection)
      //  multi = (GeometryCollection)geometry;
      //else
      //{
      //  GeometryFactory gf = new GeometryFactory(geometry.PrecisionModel);
      //  multi = gf.CreateMultiPolygon(new Polygon[] { (Polygon)geometry });
      //}


      //BinaryWriter file = new BinaryWriter(new MemoryStream());


      //file.Write((int)ShapeGeometryTypes.Polygon);

      //BoundingBox box = multi.BoundingBox;
      //file.Write(box.Left);
      //file.Write(box.Right);
      //file.Write(box.Bottom);
      //file.Write(box.Top);

      //int numParts = GetNumParts(multi);

      //throw new NotImplementedException();
      ////int numPoints = multi.NumPoints;
      
      
      //file.Write(numParts);
      //file.Write(numPoints);

      //// write the offsets to the points
      //int offset = 0;
      //for (int part = 0; part < multi.Count; part++)
      //{
      //  // offset to the shell points
      //  Polygon polygon = (Polygon)multi[part];
      //  file.Write(offset);
      //  offset = offset + ((LineRing)polygon.Exterior).coordList.Count;

      //  // offstes to the holes
      //  foreach (LineRing ring in polygon.Holes)
      //  {
      //    file.Write(offset);
      //    offset = offset + ring.coordList.Count;
      //  }
      //}

      //// write the points 
      //for (int part = 0; part < multi.NumGeometries; part++)
      //{
      //  Polygon poly = (Polygon)multi.Geometries[part];
      //  Coordinate[] points = poly.ExteriorRing.Coordinates;
      //  WriteCoords(new CoordinateList(points), file, geometryFactory);
      //  foreach (LinearRing ring in poly.InteriorRings)
      //  {
      //    Coordinate[] points2 = ring.Coordinates;
      //    WriteCoords(new CoordinateList(points2), file, geometryFactory);
      //  }
      //}
    }






    /// <summary>
    /// 
    /// </summary>
    /// <param name="points"></param>
    /// <param name="file"></param>
    /// <param name="geometryFactory"></param>
    private void WriteCoords(CoordinateList points, System.IO.BinaryWriter file)
    {
      Coordinate external;
      foreach (Coordinate point in points)
      {
        // external = geometryFactory.PrecisionModel.ToExternal(point);
        external = point;
        file.Write(external.X);
        file.Write(external.Y);
      }
    }




    /// <summary>
    /// Gets the length of the shapefile record using the geometry passed in.
    /// </summary>
    /// <param name="geometry">The geometry to get the length for.</param>
    /// <returns>The length in bytes this geometry is going to use when written out as a shapefile record.</returns>
    public int GetLength(GeometryBase geometry)
    {
      throw new NotImplementedException();

      //int numParts = GetNumParts(geometry);
      //return (22 + (2 * numParts) + geometry.NumPoints * 8);
    }





    /// <summary>
    /// 
    /// </summary>
    /// <param name="geometry"></param>
    /// <returns></returns>
    private int GetNumParts(GeometryBase geometry)
    {
      int numParts = 0;
      if (geometry is MultiArea)
      {
        MultiArea mpoly = geometry as MultiArea;
        foreach (Polygon poly in mpoly)
          numParts = numParts + poly.Holes.Count + 1;
      }
      else if (geometry is Polygon)
        numParts = ((Polygon)geometry).Holes.Count + 1;
      else throw new InvalidOperationException("Should not get here.");
      return numParts;
    }





    /// <summary>
    /// Test if a point is in a list of coordinates.
    /// </summary>
    /// <param name="testPoint">TestPoint the point to test for.</param>
    /// <param name="pointList">PointList the list of points to look through.</param>
    /// <returns>true if testPoint is a point in the pointList list.</returns>
    private bool PointInList(Coordinate testPoint, CoordinateList pointList)
    {
      foreach (Coordinate p in pointList)
        if (p.Equals(testPoint,Dimension.dim2D))
          return true;
      return false;
    }
  }
}
